﻿/*
 * Copyright (c) United Binary LLC.  All rights reserved.
 * 
 * This code is licensed under the MIT License
 * 
 * SEE: http://harnessit.codeplex.com/license
 * 
 */
using System;
using System.Drawing;

namespace UnitedBinary.UnitTests.Framework
{
	public delegate bool HarnessItComparer<T>(T t1, T t2);

	class TestExpressions : Is, Are
	{
		private const int MaxStringValueLength = 150;
		private TestMethodRecord tmr;

		public TestExpressions(TestMethodRecord tmr)
		{
			this.tmr = tmr;
		}

		public bool Equal(string expected, string actual)
		{
			return Equal( expected, actual, (string)null );
		}

		public bool Equal(string expected, string actual, string description)
		{
			return Equal( expected, actual, false, description );
		}

		public bool NotEqual(string expected, string actual)
		{
			return NotEqual( expected, actual, (string)null );
		}

		public bool NotEqual(string expected, string actual, string description)
		{
			return NotEqual( expected, actual, false, description );
		}

		public bool Equal(string expected, string actual, bool ignoreCase)
		{
			return Equal( expected, actual, ignoreCase, (string)null );
		}

		public bool Equal(string expected, string actual, bool ignoreCase, string description)
		{
			return Equal( expected, actual, (e, a) => string.Compare( e, a, ignoreCase ) == 0, description );
		}

		public bool NotEqual(string expected, string actual, bool ignoreCase)
		{
			return NotEqual( expected, actual, ignoreCase, (string)null );
		}

		public bool NotEqual(string expected, string actual, bool ignoreCase, string description)
		{
			return NotEqual( expected, actual, (e, a) => string.Compare( e, a, ignoreCase ) == 0, description );
		}

		public bool Equal(short expected, short actual)
		{
			return Equal( expected, actual, (string)null );
		}

		public bool Equal(short expected, short actual, string description)
		{
			return Equal( expected, actual, (e, a) => e == a, description );
		}

		public bool NotEqual(short expected, short actual)
		{
			return NotEqual( expected, actual, (string)null );
		}

		public bool NotEqual(short expected, short actual, string description)
		{
			return NotEqual( expected, actual, (e, a) => e == a, description );
		}

		public bool Equal(int expected, int actual)
		{
			return Equal( expected, actual, (string)null );
		}

		public bool Equal(int expected, int actual, string description)
		{
			return Equal<int>( expected, actual, (e, a) => e == a, description );
		}

		public bool NotEqual(int expected, int actual)
		{
			return NotEqual( expected, actual, (string)null );
		}

		public bool NotEqual(int expected, int actual, string description)
		{
			return NotEqual<int>( expected, actual, (e, a) => e == a, description );
		}

		public bool Equal(long expected, long actual)
		{
			return Equal( expected, actual, (string)null );
		}

		public bool Equal(long expected, long actual, string description)
		{
			return Equal<long>( expected, actual, (e, a) => e == a, description );
		}

		public bool NotEqual(long expected, long actual)
		{
			return NotEqual( expected, actual, (string)null );
		}

		public bool NotEqual(long expected, long actual, string description)
		{
			return NotEqual<long>( expected, actual, (e, a) => e == a, description );
		}

		public bool Equal(ushort expected, ushort actual)
		{
			return Equal( expected, actual, (string)null );
		}

		public bool Equal(ushort expected, ushort actual, string description)
		{
			return Equal<ushort>( expected, actual, (e, a) => e == a, description );
		}

		public bool NotEqual(ushort expected, ushort actual)
		{
			return NotEqual( expected, actual, (string)null );
		}

		public bool NotEqual(ushort expected, ushort actual, string description)
		{
			return NotEqual<ushort>( expected, actual, (e, a) => e == a, description );
		}

		public bool Equal(uint expected, uint actual)
		{
			return Equal( expected, actual, (string)null );
		}

		public bool Equal(uint expected, uint actual, string description)
		{
			return Equal<uint>( expected, actual, (e, a) => e == a, description );
		}

		public bool NotEqual(uint expected, uint actual)
		{
			return NotEqual( expected, actual, (string)null );
		}

		public bool NotEqual(uint expected, uint actual, string description)
		{
			return NotEqual<uint>( expected, actual, (e, a) => e == a, description );
		}

		public bool Equal(ulong expected, ulong actual)
		{
			return Equal( expected, actual, (string)null );
		}

		public bool Equal(ulong expected, ulong actual, string description)
		{
			return Equal<ulong>( expected, actual, (e, a) => e == a, description );
		}

		public bool NotEqual(ulong expected, ulong actual)
		{
			return NotEqual( expected, actual, (string)null );
		}

		public bool NotEqual(ulong expected, ulong actual, string description)
		{
			return NotEqual<ulong>( expected, actual, (e, a) => e == a, description );
		}

		public bool Equal(DateTime expected, DateTime actual)
		{
			return Equal( expected, actual, (string)null );
		}

		public bool Equal(DateTime expected, DateTime actual, string description)
		{
			return Equal<DateTime>( expected, actual, (e, a) => e == a, description ); 
		}

		public bool NotEqual(DateTime expected, DateTime actual)
		{
			return NotEqual( expected, actual, (string)null );
		}

		public bool NotEqual(DateTime expected, DateTime actual, string description)
		{
			return NotEqual<DateTime>( expected, actual, (e, a) => e == a, description ); 
		}

		public bool Equal(Guid expected, Guid actual)
		{
			return Equal( expected, actual, (string)null );
		}

		public bool Equal(Guid expected, Guid actual, string description)
		{
			return Equal<Guid>( expected, actual, (e, a) => e == a, description );
		}

		public bool NotEqual(Guid expected, Guid actual)
		{
			return NotEqual( expected, actual, (string)null );
		}

		public bool NotEqual(Guid expected, Guid actual, string description)
		{
			return NotEqual<Guid>( expected, actual, (e, a) => e == a, description );
		}

		public bool Equal(decimal expected, decimal actual, decimal delta)
		{
			return Equal( expected, actual, delta, (string)null );
		}

		public bool Equal(decimal expected, decimal actual, decimal delta, string description)
		{
			return Equal<decimal>( expected, actual, (e, a) => Math.Abs( e - a ) <= Math.Abs( delta ), description );
		}

		public bool NotEqual(decimal expected, decimal actual, decimal delta)
		{
			return NotEqual( expected, actual, delta, (string)null );
		}

		public bool NotEqual(decimal expected, decimal actual, decimal delta, string description)
		{
			return NotEqual<decimal>( expected, actual, (e, a) => Math.Abs( e - a ) <= Math.Abs( delta ), description );
		}

		public bool Equal(float expected, float actual, float delta)
		{
			return Equal( expected, actual, delta, (string)null );
		}

		public bool Equal(float expected, float actual, float delta, string description)
		{
			return Equal<float>( expected, actual, (e, a) => Math.Abs( e - a ) <= Math.Abs( delta ), description );
		}

		public bool NotEqual(float expected, float actual, float delta)
		{
			return NotEqual( expected, actual, delta, (string)null );
		}

		public bool NotEqual(float expected, float actual, float delta, string description)
		{
			return NotEqual<float>( expected, actual, (e, a) => Math.Abs( e - a ) <= Math.Abs( delta ), description );
		}

		public bool Equal(double expected, double actual, double delta)
		{
			return Equal( expected, actual, delta, (string)null );
		}

		public bool Equal(double expected, double actual, double delta, string description)
		{
			return Equal<double>( expected, actual, (e, a) => Math.Abs( e - a ) <= Math.Abs( delta ), description );
		}

		public bool NotEqual(double expected, double actual, double delta)
		{
			return NotEqual( expected, actual, delta, (string)null );
		}

		public bool NotEqual(double expected, double actual, double delta, string description)
		{
			return NotEqual<double>( expected, actual, (e, a) => Math.Abs( e - a ) <= Math.Abs( delta ), description );
		}

		public bool Equal(Point expected, Point actual)
		{
			return Equal( expected, actual, (string)null );
		}

		public bool Equal(Point expected, Point actual, string description)
		{
			return Equal( expected, actual, (e, a) => a == e, description );
		}

		public bool NotEqual(Point expected, Point actual)
		{
			return NotEqual( expected, actual, (string)null );
		}

		public bool NotEqual(Point expected, Point actual, string description)
		{
			return NotEqual( expected, actual, (e, a) => a == e, description );
		}

		public bool Equal(Size expected, Size actual)
		{
			return Equal( expected, actual, (string)null );
		}

		public bool Equal(Size expected, Size actual, string description)
		{
			return Equal( expected, actual, (e, a) => a == e, description );
		}

		public bool NotEqual(Size expected, Size actual)
		{
			return NotEqual( expected, actual, (string)null );
		}

		public bool NotEqual(Size expected, Size actual, string description)
		{
			return NotEqual( expected, actual, (e, a) => a == e, description );
		}

		public bool Equal(Rectangle expected, Rectangle actual)
		{
			return Equal( expected, actual, (string)null );
		}

		public bool Equal(Rectangle expected, Rectangle actual, string description)
		{
			return Equal( expected, actual, (e, a) => a == e, description );
		}

		public bool NotEqual(Rectangle expected, Rectangle actual)
		{
			return NotEqual( expected, actual, (string)null );
		}

		public bool NotEqual(Rectangle expected, Rectangle actual, string description)
		{
			return NotEqual( expected, actual, (e, a) => a == e, description );
		}

		public bool Equal(RectangleF expected, RectangleF actual, float delta)
		{
			return Equal( expected, actual, delta, (string)null );
		}

		public bool Equal(RectangleF expected, RectangleF actual, float delta, string description)
		{
			return Equal<RectangleF>( expected, actual, 
				(e, a) => 
					Math.Abs( e.Top - a.Top ) <= Math.Abs( delta ) && 
					Math.Abs( e.Left - a.Left ) <= Math.Abs( delta ) && 
					Math.Abs( e.Width - a.Width) <= Math.Abs( delta ) && 
					Math.Abs( e.Height - a.Height) <= Math.Abs( delta ), 
				description );
		}

		public bool NotEqual(RectangleF expected, RectangleF actual, float delta)
		{
			return NotEqual( expected, actual, delta, (string)null );
		}

		public bool NotEqual(RectangleF expected, RectangleF actual, float delta, string description)
		{
			return NotEqual<RectangleF>( expected, actual, 
				(e, a) => 
					Math.Abs( e.Top - a.Top ) <= Math.Abs( delta ) && 
					Math.Abs( e.Left - a.Left ) <= Math.Abs( delta ) && 
					Math.Abs( e.Width - a.Width) <= Math.Abs( delta ) && 
					Math.Abs( e.Height - a.Height) <= Math.Abs( delta ), 
				description );
		}

		public bool Equal(PointF expected, PointF actual, float delta)
		{
			return Equal( expected, actual, delta, (string)null );
		}

		public bool Equal(PointF expected, PointF actual, float delta, string description)
		{
			return Equal<PointF>( expected, actual, 
				(e, a) => Math.Abs( e.X - a.X ) <= Math.Abs( delta ) && Math.Abs( e.Y - a.Y ) <= Math.Abs( delta ), 
				description );
		}

		public bool NotEqual(PointF expected, PointF actual, float delta)
		{
			return NotEqual( expected, actual, delta, (string)null );
		}

		public bool NotEqual(PointF expected, PointF actual, float delta, string description)
		{
			return NotEqual<PointF>(  expected, actual, 
				(e, a) => Math.Abs( e.X - a.X ) <= Math.Abs( delta ) && Math.Abs( e.Y - a.Y ) <= Math.Abs( delta ), 
				description );
		}

		public bool Equal(SizeF expected, SizeF actual, float delta)
		{
			return Equal( expected, actual, delta, (string)null );
		}

		public bool Equal(SizeF expected, SizeF actual, float delta, string description)
		{
			return Equal<SizeF>( expected, actual, 
				(e, a) => Math.Abs( e.Width - a.Width ) <= Math.Abs( delta ) && Math.Abs( e.Height - a.Height ) <= Math.Abs( delta ), 
				description );
		}

		public bool NotEqual(SizeF expected, SizeF actual, float delta)
		{
			return NotEqual( expected, actual, delta, (string)null );
		}

		public bool NotEqual(SizeF expected, SizeF actual, float delta, string description)
		{
			return NotEqual<SizeF>(  expected, actual, 
				(e, a) => Math.Abs( e.Width - a.Width ) <= Math.Abs( delta ) && Math.Abs( e.Height - a.Height ) <= Math.Abs( delta ), 
				description );
		}

		public bool Equal<T>(T expected, T actual, HarnessItComparer<T> comparer)
		{
			return Equal<T>( expected, actual, comparer, null );
		}

		public bool Equal<T>(T expected, T actual, HarnessItComparer<T> comparer, string description)
		{
			bool equal = comparer( expected, actual );
			if ( string.IsNullOrEmpty( description ))
			{
				description = string.Format( "Are.Equal<{2}>: Expected <{0}>, found <{1}>.",
					GetValueText( expected, actual ), GetValueText( actual, expected ), typeof( T ).Name );
			}
			else
			{
				description = string.Format( "Are.Equal<{0}>: {1}", typeof( T ).Name, description );
			}

			return tmr.RunTest( equal, description );
		}

		public bool NotEqual<T>(T expected, T actual, HarnessItComparer<T> comparer)
		{
			return NotEqual<T>( expected, actual, comparer, null );
		}

		public bool NotEqual<T>(T expected, T actual, HarnessItComparer<T> comparer, string description)
		{
			bool equal = comparer( expected, actual );
			if ( string.IsNullOrEmpty( description ) )
			{
				description = string.Format( "Are.NotEqual<{2}>: Expected <{0}>, found <{1}>.", 
					GetValueText( expected, actual ), GetValueText( actual, expected ), typeof(T).Name);
			}
			else
			{
				description = string.Format( "Are.Equal<{0}>: {1}", typeof( T ).Name, description );
			}

			return tmr.RunTest( !equal, description );
		}

		public bool False(bool assertion)
		{
			return False( assertion, (string)null );
		}

		public bool False(bool assertion, string description)
		{
			if ( description == null && assertion)
            {
				description = "Is.False: Boolean is expected to be false.";
            }
			else
			{
				description = "Is.False: " + description;
			}
			return tmr.RunTest( !assertion, description );
		}

		public bool True(bool assertion)
		{
			return True( assertion, (string)null );
		}

		public bool True(bool assertion, string description)
		{
			if ( description == null )
			{
				description = "Is.True: Boolean is expected to be true.";
			}
			else
			{
				description = "Is.True: " +description;
			}
			return tmr.RunTest( assertion, description );
		}

		public bool Null(object obj)
		{
			return Null( obj, (string)null );
		}

		public bool Null(object obj, string description)
		{
			if ( description == null)
			{
				description = "Is.Null: Object is expected to be null.";
			}
			else
			{
				description = "Is.Null: " + description;
			}
			return tmr.RunTest( obj == null, description );
		}

		public bool NotNull(object obj)
		{
			return NotNull( obj, (string)null );
		}

		public bool NotNull(object obj, string description)
		{
			if ( description == null)
			{
				description = "Is.NotNull: Object is expected to be not null.";
			}
			else
			{
				description = "Is.NotNull: " + description;
			}
			return tmr.RunTest( obj != null, description );
		}

		private string GetValueText<T>(T val, T other)
		{
			object obj = val;
			object obj2 = other;
			string text = obj == null ? "NULL" : obj.ToString();
			string otherText = obj2 == null ? "NULL" : obj2.ToString();

			if ( typeof( T ) == typeof( string ) )
			{
				int textLength = text.Length;
				if ( text == otherText || textLength < MaxStringValueLength )
				{
					text = GetTrimmedText( text );
				}
				else if ( textLength > 0 )
				{
					// Find the differences and show that section of the strings.
					int diffIndex = -1;
					for ( int i = 0; i < textLength && i < otherText.Length; i++ )
					{
						diffIndex = i;
						if ( text[i] != otherText[i] )
						{
							break;
						}
					}

					int start = Math.Max( 0, diffIndex - 20 );
					int end = Math.Min( textLength - 1, diffIndex + 20 );

					if (end <= start)
					{
						text = "";
					}
					else
					{
						text = text.Substring( start, end-start );
					}

					if ( start != 0 )
					{
						text = "..." + text;
					}

					if ( end < textLength - 1 )
					{
						text = text + "...";
					}
				}
			}

			return text;
		}

		private string GetTrimmedText(string text)
		{
			if ( text.Length > MaxStringValueLength )
			{
				return text.Substring( 0, MaxStringValueLength ) + "...";
			}

			return text;
		}

		public bool NotNullOrEmpty(string text)
		{
			return NotNullOrEmpty( text, null );
		}

		public bool NotNullOrEmpty(string text, string description)
		{
			bool equal = string.IsNullOrEmpty( text );
			if ( string.IsNullOrEmpty( description ) )
			{
				description = string.Format( "Is.NotNullOrEmpty: Expected <{0}>, found <{1}>.",
					GetValueText( null, text ), GetValueText( null, text ));
			}
			else
			{
				description = string.Format("Is.NotNullOrEmpty: string is {0}.",
					text == null ? "NULL" : "String.Empty" );
			}

			return tmr.RunTest( equal, description );
		}
	}
}
